package com.ydlclass;

import java.util.concurrent.atomic.LongAdder;


public class IdGenerator {

    // 雪花算法 -- 世界上没有一个片雪花是一样的
    // 机房号（数据中心） 5bit  32
    // 机器号           5bit  32
    // 时间戳（long 1970-1-1） 原本64位表示的时间，必须减少到42位，自由选择一个比较近的时间
    // 比如我们公司成立的日期
    // 同一个机房的同一个机器号的同一个时间可以因为并发量很大需要多个id（序列号）
    // 序列号   12bit      5+5+42+12 = 64（机房号+机器号+时间戳+序列号）
    
    // 起始时间戳
    public static final long START_STAMP = DateUtil.get("2022-1-1").getTime();

    //机房号、机器号、序列号的最大位数
    public static final long DATA_CENTER_BIT = 5L;
    public static final long MACHINE_BIT = 5L;
    public static final long SEQUENCE_BIT = 12L;
    
    // 机房号、机器号、序列号的最大值（位运算性能高）
    public static final long DATA_CENTER_MAX = ~(-1L << DATA_CENTER_BIT);
    public static final long MACHINE_MAX = ~(-1L << MACHINE_BIT);
    public static final long SEQUENCE_MAX = ~(-1L << SEQUENCE_BIT);
    
    
    // 时间戳（42位）、机房号（5位）、机器号（5位）、序列号（12位）
    // 雪花算法：101010101010101010101010101010101010101011 10101 10101 101011010101

    //定义时间戳、机房号、机器号左移的位数
    public static final long TIMESTAMP_LEFT = DATA_CENTER_BIT + MACHINE_BIT + SEQUENCE_BIT;
    public static final long DATA_CENTER_LEFT = MACHINE_BIT + SEQUENCE_BIT;
    public static final long MACHINE_LEFT = SEQUENCE_BIT;

    //机房号
    private long dataCenterId;
    //机器号
    private long machineId;
    //序列号
    private LongAdder sequenceId = new LongAdder(); //解决线程安全问题

    // 时钟回拨的问题，我们需要去处理
    private long lastTimeStamp = -1L;
    
    public IdGenerator(long dataCenterId, long machineId) {
        // 判断传世的参数是否合法
        if(dataCenterId > DATA_CENTER_MAX || machineId > MACHINE_MAX){
            throw new IllegalArgumentException("您所传入的机房编号或机器编号不合法！");
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }
    
    public long getId(){
        // 第一步：处理时间戳的问题
        long currentTime = System.currentTimeMillis();
        
        //定义时间戳（差值有利于扩大时间范围）
        long timeStamp = currentTime - START_STAMP;
        
        // 判断时钟回拨
        if(timeStamp < lastTimeStamp){
            throw new RuntimeException("您的服务器进行了时钟回调.");
        }
        
        // sequenceId需要做一些处理，如果是同一个时间节点，必须自增
        if (timeStamp == lastTimeStamp){
            sequenceId.increment();
            //解决高并发场景下同一时刻序列号用光的问题
            if(sequenceId.sum() >= SEQUENCE_MAX){ //说明同一时刻sequenceId已经用光了
                timeStamp = getNextTimeStamp(); //获取下一个时间戳
                sequenceId.reset();
            }
        } else {
            sequenceId.reset();
        }
        
        // 执行结束将时间戳赋值给lastTimeStamp
        lastTimeStamp = timeStamp; 
        long sequence = sequenceId.sum(); //序列号

        //返回雪花算法生成的号码（时间戳+机房号+机器号+序列号）
        return timeStamp << TIMESTAMP_LEFT |  dataCenterId << DATA_CENTER_LEFT
            | machineId << MACHINE_LEFT | sequence;
        
    }
    
    private long getNextTimeStamp() {
        // 获取当前的时间戳
        long current = System.currentTimeMillis() - START_STAMP; //当前时间-起始时间戳
        // 如果一样就一直循环，直到下一个时间戳
        while (current == lastTimeStamp){ //自旋等待
            current = System.currentTimeMillis() - START_STAMP;
        }
        return current;
    }
    
    public static void main(String[] args) {
        IdGenerator idGenerator = new IdGenerator(1,2);
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> System.out.println(idGenerator.getId())).start();
        }
    }
    
}
